import config from '../../../config/config.js'

// components
import atyInput from '../input/index.vue'
import Drop from '../select/dropdown/index.vue'
import clickoutside from '../../../directives/clickoutside.js'
import TransferDom from '../../../directives/transfer-dom.js'
import { DEFAULT_FORMATS, TYPE_VALUE_RESOLVER_MAP , parseToDate} from './util.js'
import DatePickerPanel from './panel/date/date.vue'
import RangeDatePickerPanel from './panel/date/date-range.vue'
import TimePickerPanel from './panel/time/time.vue'
import RangeTimePickerPanel from './panel/time/time-range.vue'

// mixins
import formItem from '../form-item/form-item.js'
// import FormItemProps from '../../../mixins/form-item-props.js'
import Emitter from '../../../mixins/emitter.js'

// prefix
const prefixCls = config.prefix + '-date-picker'

export default {
  name: 'CalendarPicker',
  mixins: [formItem, /*FormItemProps,*/ Emitter],
  components: { atyInput, Drop, DatePickerPanel, RangeDatePickerPanel, TimePickerPanel, RangeTimePickerPanel },
  directives: { clickoutside, TransferDom },
  props: {
    /**
     * #property
     *
     * @name    dropAsync
     * @cn      下拉菜单异步渲染
     * @group   基本属性
     * @type    boolean
     * @bind    must
     * @description 下拉菜单是否异步渲染dom，默认true
     */
    dropAsync: {
      type: Boolean,
      default: true
    },
    /**
     * #property
     *
     * @name    isTimestamp
     * @cn      是否时间戳
     * @group   基本属性
     * @type    boolean
     * @description value的初始值是否是时间戳
     */
    isTimestamp: Boolean,
    /**
     * #property
     *
     * @name    format
     * @cn      展示的日期|时间格式
     * @group   基本属性
     * @type    string
     * @description  展示的日期|时间格式
     */
    format: {
      type: String
    },
    /**
     * #property
     *
     * @name    readonly
     * @cn      是否只读
     * @group   控制
     * @type    boolean
     * @value  false
     * @description  是否只读
     */
    readonly: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    disabled
     * @cn      是否禁用
     * @group   控制
     * @type    boolean
     * @value  false
     * @description 是否禁用选择器
     */
    disabled: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    editable
     * @cn      是否可编辑
     * @group   控制
     * @type    boolean
     * @bind  must
     * @value  true
     * @description 文本框是否可以输入，只在没有使用 slot 时有效
     */
    editable: {
      type: Boolean,
      default: true
    },
    /**
     * #property
     *
     * @name    clearable
     * @cn      是否显示清除按钮
     * @group   控制
     * @type    boolean
     * @bind  must
     * @value  true
     * @description 文本框是否显示清除按钮
     */
    clearable: {
      type: Boolean,
      default: true
    },
    /**
     * #property
     *
     * @name    confirm
     * @cn      是否显示确定按钮
     * @group   控制
     * @type    boolean
     * @value  false
     * @description 文本框是否显示确定按钮
     */
    confirm: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    open
     * @cn      是否显示时间|日期组件
     * @group   控制
     * @type    boolean
     * @value   false
     * @description 手动控制时间|日期选择器的显示状态，true 为显示，false 为收起。
     *              使用该属性后，选择器不会主动关闭。建议配合 slot 及 confirm 和相关事件一起使用
     */
    open: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    multiple
     * @cn      多选
     * @group   控制
     * @type    boolean
     * @value   false
     * @description 日期多选
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    timePickerOptions
     * @cn      时间选择额外配置
     * @group   基本属性
     * @bind   must
     * @type    object
     * @description 时间选择器额外配置，比如不可选时间与步进等
     * {
     *     disabledHours: [0,1,2,3,4], // 禁用的小时
     *     disabledMinutes: [0,1,2,3,4], // 禁用的分钟
     *     disabledSeconds: [0,1,2,3,4], // 禁用的秒
     *     hideDisabledOptions: true, // 隐藏禁用的时间
     *     steps: [2,2,2], // 时分秒候选项步长
     *     defaultHour: 0, // 选择日期后默认小时
     *     defaultMinute: 0, // 选择日期后默认分钟
     *     defaultSecond: 0, // 选择日期后默认秒
     *     autoSwitch: false, // 选择日期后自动切换到timePicker
     * }
     */
    timePickerOptions: {
      type: Object,
      default: () => ({})
    },
    /**
     * #property
     *
     * @name    splitPanels
     * @cn      是否分开下拉面板
     * @group   控制
     * @type    boolean
     * @value   false
     * @description type为范围时，使用该属性后日期面板选择不再绑定只相差一个月
     */
    splitPanels: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    showWeekNumbers
     * @cn      显示周数
     * @group   控制
     * @type    boolean
     * @value   false
     * @description 开启后将在日期左侧显示周数
     */
    showWeekNumbers: {
      type: Boolean,
      default: false
    },
    /**
     * #property
     *
     * @name    startDate
     * @cn      开始日期
     * @group   控制
     * @type    Object
     * @bind    must
     * @description date类型, 如不指定则默认展开的日期候选项为今日
     */
    startDate: {
      type: Date
    },
    /**
     * #property
     *
     * @name    placeholder
     * @cn      占位文本
     * @group   基本属性
     * @type    string
     * @description 占位文本
     */
    placeholder: {
      type: String,
      default: ''
    },
    /**
     * #property
     *
     * @name    placement
     * @cn      时间|日期选择器出现的位置
     * @group   基本属性
     * @type    list
     * @options [top, top-start, top-end,
     * bottom, bottom-start, bottom-end,
     * left, left-start, left-end,
     * right, right-start, right-end]
     * @value  bottom-start
     * @description 该时间|日期选择器出现的位置，取值为 ['top', 'top-start', 'top-end',
     * 'bottom', 'bottom-start', 'bottom-end',
     * 'left', 'left-start', 'left-end',
     * 'right', 'right-start', 'right-end']中的其中一个
     */
    placement: {
      validator (value) {
        return Artery.isOneOf(value, [
          'top', 'top-start', 'top-end',
          'bottom', 'bottom-start', 'bottom-end',
          'left', 'left-start', 'left-end',
          'right', 'right-start', 'right-end']
        )
      },
      default: 'bottom-start'
    },
    /**
     * #property
     *
     * @name    transfer
     * @cn      是否将弹层放置于 body 内
     * @group   控制
     * @type    boolean
     * @bind  must
     * @value  true
     * @description  是否将弹层放置于 body 内，
     *              在 Tabs、带有 fixed 的 Table 列内使用时，建议添加此属性，它将不受父级样式影响，从而达到更好的效果
     */
    transfer: {
      type: Boolean,
      default: true
    },
    /**
     * #property
     *
     * @name    elementId
     * @cn      input id
     * @group   基本属性
     * @type    string
     * @description 该时间|日期输入框上的id
     */
    elementId: {
      type: String
    },
    /**
     * #property
     *
     * @name    options
     * @cn      日期选择额外配置
     * @group   基本属性
     * @bind   must
     * @type    object
     * @description 选择器额外配置，比如不可选日期与快捷选项
     *      ｛
     * shortcuts：｛
     * text | String： //显示的文案
     * value | Function：//返回指定的日期，如需自己控制逻辑，可不设置，并使用 onClick 回调
     * onClick | Function：//点击时的回调，参数为当前日期选择器的 Vue 实例，当需要自定义复杂操作时，可以使用
     * ｝
     * // 设置不可选择的日期，参数为当前的日期，需要返回 Boolean 是否禁用这天
     * disabledDate：
     *      ｝
     */
    options: {
      type: Object,
      default: () => ({})
    }
  },
  data () {
    const isRange = this.type.includes('range')
    const emptyArray = isRange ? [null, null] : [null]
    // 是否是空数组
    let initialValue
    if (this.form && this.form.value && this.prop) {
      initialValue = this.parseDate(this.form.value[this.prop])
    } else {
      initialValue = Artery.isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value)
    }

    return {
      dropLoaded: !this.dropAsync || (this.type.includes('range') && this.value && this.value[0]),
      prefixCls: prefixCls,
      showClose: false,
      visible: this.open,
      // preVisible: false, // 点击日历图标之前是不是显示的，为了解决点击日历的bug，野路子解决方案。希望后续能改掉。
      internalValue: initialValue,
      disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
      disableCloseUnderTransfer: false, // transfer 模式下，点击Drop也会触发关闭,
      selectionMode: this.onSelectionModeChange(this.type),
      // forceInputRerender: 1
    }
  },
  computed: {
    wrapperClasses() {
      return [prefixCls].concat(this.formItemClasses)
    },
    dropdownCls () {
      return [
        this.elementId ? prefixCls + this.elementId : '',
        {
          [prefixCls + '-transfer']: this.transfer
        }]
    },
    publicStringValue () {
      const isTimestamp = this.isTimestamp
      const isRange = this.type.includes('range')
      if (!this.internalValue) {
        return isRange ? [] : ''
      }
      const val = this.internalValue.map(date => date instanceof Date ? new Date(date) : (date || ''))
      if (Artery.isEmptyArray(val)) {
        return isRange ? [] : ''
      }
      if (isRange) {
        return val.map(date => isTimestamp ? this.dateToLong(date) : this.dateToVModelString(date))
      }
      if (this.multiple) {
        return isTimestamp ? val.map(date => this.dateToLong(date)).join(',') : this.dateToVModelString(val)
      }
      return isTimestamp ? this.dateToLong(val[0]) : this.dateToVModelString(val[0])
    },
    iconType () {
      let icon = 'ios-calendar-outline'
      if (this.type === 'time' || this.type === 'timerange') icon = 'ios-clock-outline'
      if (this.showClose) icon = 'ios-close'
      return icon
    },
    transition () {
      const bottomPlaced = this.placement.match(/^bottom/)
      return bottomPlaced ? 'slide-up' : 'slide-down'
    },
    visualValue () {
      return this.formatDate(this.internalValue)
    },
    isConfirm () {
      return this.confirm || this.type === 'datetime' || this.type === 'datetimerange' || this.type === 'onlytimerange' || this.multiple
    },
    reference() {
      return this.$refs.input.$refs.input
    }
  },
  methods: {
    handleClear () {
      this.visible = false
      this.internalValue = []
      /**
       * #event
       *
       * @name    clear
       * @body
       * @description  点击清空按钮时触发
       */
      this.$emit('clear')
      this.emitChange()
      this.panelReset()

      setTimeout(
        () => this.onSelectionModeChange(this.type),
        500 // delay to improve dropdown close visual effect
      )
    },
    // handleInput (val) {
    //   this.$emit('input', val)
    // },
    handleChange (currentValue) {
      const isArrayValue = this.type.includes('range') || this.multiple
      const oldValue = this.visualValue
      const newValue = currentValue
      const newDate = this.parseDate(newValue)
      const disabledDateFn =
        this.options &&
        typeof this.options.disabledDate === 'function' &&
        this.options.disabledDate
      const valueToTest = isArrayValue ? newDate : newDate[0]
      const isDisabled = disabledDateFn && disabledDateFn(valueToTest)
      const isValidDate = newDate.reduce((valid, date) => valid && date instanceof Date, true)

      if (newValue !== oldValue && !isDisabled && isValidDate) {
        if (this.internalValue.toString() === newDate.toString()) { // 时间没变化, 可能是手动输入了无效的字符, 清除掉 fixed->http://code.thunisoft.com/artery/artery-ui/issues/127
          this.$refs.input.$refs.input.value = oldValue
        } else {
          this.internalValue = newDate
          this.emitChange()
        }
      } else {
        // this.forceInputRerender++
      }
    },
    /** 异步组件加载之后的回调 */
    asyncComponentLoaded (componentName) {
      switch (componentName) {
        case 'drop':
          this.$refs.pickerPanel.onToggleVisibility(this.visible)
          // this.$refs.pickerPanel.resetPanel && this.$refs.pickerPanel.resetPanel()
          if (this.visible) {
            this.$refs.drop.update()
          }
          break
      }
      /*
       * this.$nextTick(function() {
       *     // 更新下拉框的位置
       *     this.broadcast(config.prefix + '-select-dropdown', 'update-popper');
       * })
       */
    },
    onSelectionModeChange (type) {
      if (type.match(/^date/) || type.match(/^only/)) type = 'date'
      this.selectionMode = type.replace('range', '')
      return this.selectionMode
    },
    // 开启 transfer 时，点击 Drop 即会关闭，这里不让其关闭
    handleTransferClick () {
      /*
       * if (this.transfer) {
       * this.disableCloseUnderTransfer = true;
       *}
       */
    },
    handleClose () {
      /*
       *  if (this.disableCloseUnderTransfer) {
       * this.disableCloseUnderTransfer = false;
       * return false;
       *}
       */
      this.visible = false
      if (this.open !== null) {
        return
      }
      this.disableClickOutSide = false
    },
    handleFocus () {
      this.initDrop()
      this.visible = true
    },
    handleBlur() {
      this.handleInputLeaveEvent()
      this.validate()
    },
    handleKeydown(event) {
      this._keyMode = true
      const keyCode = event.keyCode
      if (event.keyCode === 9) {
        this.handleClose()
      }
    },
    reset () {
      this.internalValue = this._originalInternalValue
      this.panelReset()
    },
    panelReset () {
      this.$refs.pickerPanel && this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset()
    },
    handleInputMouseenter () {
      if (this.readonly || this.disabled) return
      if (this.visualValue && this.clearable) {
        this.showClose = true
      }
    },
    handleInputMouseleave () {
      this.handleInputLeaveEvent()
      this.showClose = false
    },
    handleInputLeaveEvent(){
      if (this._keyMode) {
        const value = this.$refs.input.$refs.input.value
        this.handleChange(value)
        delete this._keyMode
      }
    },
    handleIconClick () {
      if (this.showClose) {
        this.handleClear()
        this.dropLoaded = true
      } else if (!this.disabled) {
        if (this.preVisible) { // 为了解决一个点击小图标的bug
          return
        }
        this.handleFocus()
        // input重新获取焦点，修改点击小图标后，在切换其他的datePicker,弹出层不消失问题
        // this.$children[0].focus()
      }
    },
    emitChange () {
      this.$nextTick(() => {
        this.$emit('input', this.publicStringValue)
        /**
         * #event
         *
         * @name    change
         * @param   value  当前的值
         * @body
         * @description  日期改变时触发
         */
        this.$emit('change', this.publicStringValue)
      })
    },
    parseTimeStamp (val) {
      return new Date(Number(val))
    },
    dateToLong (data) {
      return data ? data.getTime() : null
    },
    dateToVModelString (value) {
      const format = DEFAULT_FORMATS[this.type]
      if (this.multiple) {
        const formatter = TYPE_VALUE_RESOLVER_MAP.multiple.formatter
        return formatter(value, format)
      } else {
        const { formatter } = (
          TYPE_VALUE_RESOLVER_MAP[this.type] ||
          TYPE_VALUE_RESOLVER_MAP.default
        )
        return formatter(value, format)
      }
    },
    parseDate (time) {
      if (Artery.type(time) === 'number') {
        return this.parseLongToData(time)
      } else {
        return this.parseStringToData(time)
      }
    },
    parseStringToData (val) {
      const isRange = this.type.includes('range')
      const type = this.type
      const parser = (
        TYPE_VALUE_RESOLVER_MAP[type] ||
        TYPE_VALUE_RESOLVER_MAP.default
      ).parser
      const format = this.format || DEFAULT_FORMATS[type]
      const multipleParser = TYPE_VALUE_RESOLVER_MAP.multiple.parser

      if (val && type === 'time' && !(val instanceof Date)) {
        val = parser(val, format)
      } else if (this.multiple && val) {
        val = multipleParser(val, format)
      } else if (isRange) {
        if (!val) {
          val = [null, null]
        } else {
          if (typeof val === 'string') {
            val = parser(val, format)
          } else if (type === 'timerange') {
            val = parser(val, format)
          } else {
            val = val.map(date => {
              let dt = new Date(date)
              // 兼容IE11的一些奇奇怪怪的转化问题
              if (isNaN(dt.getTime())) {
                y
              }
              return isNaN(dt.getTime()) ? null : dt
            })
          }
        }
      } else if (typeof val === 'string' && type.indexOf('time') !== 0) {
        val = parser(val, format) || null
      }

      return (isRange || this.multiple) ? (val || []) : [val]
    },
    parseLongToData (val) {
      const isRange = this.type.includes('range')
      if (!isRange) {
        val = val === '' ? [] : String(val).split(',')
      }
      return val.map(this.parseTimeStamp)
    },
    formatDate (value) {
      const format = DEFAULT_FORMATS[this.type]

      if (this.multiple) {
        const formatter = TYPE_VALUE_RESOLVER_MAP.multiple.formatter
        return formatter(value, this.format || format)
      } else {
        const { formatter } = (
          TYPE_VALUE_RESOLVER_MAP[this.type] ||
          TYPE_VALUE_RESOLVER_MAP.default
        )
        return formatter(value, this.format || format)
      }
    },
    onPick (dates, visible = false) {
      if (this.multiple) {
        const pickedTimeStamp = dates.getTime()
        const indexOfPickedDate = this.internalValue.findIndex(date => date && date.getTime() === pickedTimeStamp)
        const allDates = [...this.internalValue, dates].filter(Boolean)
        const timeStamps = allDates.map(date => date.getTime()).filter((ts, i, arr) => arr.indexOf(ts) === i && i !== indexOfPickedDate) // filter away duplicates
        this.internalValue = timeStamps.map(ts => new Date(ts))
      } else {
        this.internalValue = Array.isArray(dates) ? dates : [dates]
        if (this.type.includes('time') && this.selectionMode == 'date') {
          const date = this.internalValue[0]
          const defHour = this.timePickerOptions.defaultHour
          const defMinute = this.timePickerOptions.defaultMinute
          const defSecond = this.timePickerOptions.defaultSecond
          if (defHour) date.setHours(date.getHours() || defHour)
          if (defMinute) date.setMinutes(date.getMinutes() || defMinute)
          if (defSecond) date.setSeconds(date.getSeconds() || defSecond)
          if (this.timePickerOptions.autoSwitch) {
            const pickerPanel = this.$refs.pickerPanel
            if (pickerPanel.currentView !== 'time') {
              pickerPanel.handleToggleTime()
            }
          }
        }
      }

      if (!this.isConfirm) this.onSelectionModeChange(this.type) // reset the selectionMode
      if (!this.isConfirm) this.visible = visible
      this.emitChange()
    },
    onPickSuccess () {
      this.visible = false
      /**
       * #event
       *
       * @name    ok
       * @body
       * @description 点击确认按钮时触发
       */
      this.$emit('ok')
      this.panelReset()
    },
    getValidateValue() {
      return this.publicStringValue
    },
    initDrop() {
      if (this.readonly) return
      // 处理异步加载
      if (this.dropLoaded) {
        this.$nextTick(()=>{
          this.$refs.pickerPanel.onToggleVisibility(true)
          this.$refs.pickerPanel.resetPanel && this.$refs.pickerPanel.resetPanel()
        })
      } else {
        this.dropLoaded = true
      }
    }
  },
  watch: {
    visible (state) {
      if (state === false) {
        // this.opened = false;
        if (!this.readonly && this.$refs.drop) {
          this.$refs.drop.destroy()
        }
        const input = this.$el.querySelector('input')
        if (input) {
          input.blur()
        }
      }
      if (state) {
        // 掉drop的更新方法
        const drop = this.$refs.drop
        if (drop) {
          drop.update()
        }
        this.preVisible = state
      } else {
        setTimeout(() => this.preVisible = false, 300)
      }
      /**
       * #event
       *
       * @name    open-change
       * @param   state  当前的状态
       * @body
       * @description 选择框弹出或者收起时触发
       */
      this.$emit('open-change', state)
    },
    value (val) {
      this.internalValue = this.parseDate(val)
    },
    open (val) {
      this.initDrop()
      this.visible = val === true
    },
    type (type) {
      this.onSelectionModeChange(type)
    },
    publicStringValue (newValue, oldValue) {
      const newValeStr = typeof newValue !== 'string' ? JSON.stringify(newValue) : newValue
      const oldValueStr = typeof oldValue !== 'string' ? JSON.stringify(oldValue) : oldValue
      const shouldEmitInput = newValeStr !== oldValueStr
      if (shouldEmitInput) {
        this.$emit('input', newValue)
        this.validate()
      }
    }
  },
  created () {
    // 保存初始值，用于reset
    this._originalInternalValue = Artery.deepCopy(this.internalValue)
  }
  /**
   * #event
   *
   * @name    pick-date-start
   * @param   date 日期类型
   * @body
   * @description 选择开始时间触发, 仅适用于range选择
   */
  /**
   * #event
   *
   * @name    pick-date-end
   * @param   date 日期类型
   * @body
   * @description  选择结束时间触发, 仅适用于range选择
   */
}
